#ifndef FORMULA_H_INCLUDED
#define FORMULA_H_INCLUDED

#include "formulaelement.h"
#include "formulafactoryutil.h"
#include "../util/linkedlist/linkedlist.h"
#include "../util/stack/stack.h"
#include "../util/realvaluecheck.h"
#include "../realnumber/realnumber.h"
#include "../input/inputiterator.h"
#include "../input/inputelement.h"
#include <string>


#include <iostream>

class Formula {
    private:
//    public: // This methods are only public because of tests.
        LinkedList<FormulaElement> *elements;
        // the elements of this formula; like + / * - and numbers
        double fitness;
        // the score of this formula

        bool invariant(bool isInbetweenMutation);
        // the classic invariant for this class
        bool invariantSpecialMutationLevelTest(const int& level);
        // special level test to track an error in mutation that occurs only every ca. 48273849 mutation
        void simplifyCalculateTwoNumbers(LinkedList<FormulaElement> *simplificationStack, LinkedList<int> *typeList);

    public: // This methods are only public because of tests.
        inline double evaluatePosition(InputElement& myInputElement, LinkedList<double>* myEvaluationStack) {
            assert(myEvaluationStack->getSize()==0);
            const double* myInputArray = myInputElement.getPositionArray();
            elements->resetIteratorPosition();
            while(elements->iteratorHasNext()) {
                elements->iteratorGetNext().evaluateMe(*myEvaluationStack, myInputArray);
            }
            assert(myEvaluationStack->getSize()==1);
            double& result = myEvaluationStack->pop();
            double temp=result-myInputElement.getValue();
            delete &result;
            if(RealValueCheck::check(temp)) {
                temp = 10000000000000000000.0;
                wasNeverNan=false;
            }
            return RealValueCheck::abs(temp);
        };

        inline void mutateAddNewElementAtIteratorPosition(int& chance, int& level, bool isCloseToStart) {
            if(chance<6) {
                elements->addAtIteratorPosition(FormulaFactoryUtil::getRandomRealNumberOrVariable());
                elements->iteratorGetNext();
                ++level;
            }
            else if((level>0) && (!isCloseToStart)) {
                elements->addAtIteratorPosition(FormulaFactoryUtil::getRandomRealOperation());
                --level;
            }
            else if(elements->iteratorHasNext())
                elements->iteratorGetNext();
        };

        inline void mutateDeleteElementAtIteratorPosition(int& chance, int& level) {
            if(chance<15 && (level>0 || !elements->getElementAtIteratorPosition().isNumber())) {
                if(elements->getElementAtIteratorPosition().isNumber())
                    --level;
                else
                    ++level;
                elements->deleteAtIteratorPosition();
            }
            else {
                if(elements->iteratorHasNext())
                    elements->iteratorGetNext();
            }
        };

        inline void mutateElementMutate() {
            FormulaElement& position=elements->getElementAtIteratorPosition();
            if(position.isNumber()) {
                position.mutate();
                if(elements->iteratorHasNext())
                    elements->iteratorGetNext();
            }
            else {
                elements->addAtIteratorPosition(FormulaFactoryUtil::getRandomRealOperation());
                elements->deleteAtIteratorPosition();
            }
        };

        inline void mutateBringLevelBackToZero(int& level) {
            assert(level>=0);
            while(level--) {
                elements->addAtEnd(FormulaFactoryUtil::getRandomRealOperation());
            }
        };

//    public:
        Formula();
        // basic constructor
        ~Formula();
        // destructor
        inline LinkedList<FormulaElement>& getListOfFormulaElements() {
            return *elements;
        };
        // returns elements
        std::string& toString();
        // classic toString()
        std::string& toHumanString();
        // classic toString() to a more human readeable formula
        void evaluate(InputIterator& myInputIterator);
        // evaluate this formula and update float fitness;
        const double& getFitness();
        // returns fitness
        void setFitness(const double& myFitness) {  fitness=myFitness;  };
        // set fitness
        void mutate();
        // mutate this formula
        Formula* copy();
        // returns a copy of this formula
        const unsigned int& getSize() { return elements->getSize(); };
        // returns the size of elements
        bool wasNeverNan;
        // stores bad reputation of this formula
        friend bool operator<(Formula& f1, Formula& f2);
        // overloading operator <
        friend bool operator>(Formula& f1, Formula& f2);
        // overloading operator >
        void simplify();
        // simplify this formula
        int& getElementNumberOfLastSimplificationStackElement(LinkedList<FormulaElement> *simplificationStack);
        // simplification helper 2
};
#endif // FORMULA_H_INCLUDED
